package com.ar4j.spring;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.PriorityOrdered;

/**
 * A Spring factory post processor that injects itself as a delegate for resolving autowiring
 * candidates. Effectively allows Ar4J to create new SpringActiveRecordAdapter instances whenever
 * one needs to be autowired
 */
public class AutowiringBeanFactoryPostProcessor implements AutowireCandidateResolver, BeanFactoryPostProcessor, PriorityOrdered {
  /**
   * Default placeholder prefix: "${" 
   */
  public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
  
  /** 
   * Default placeholder suffix: "}" 
   */
  public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
  
  private AutowireCandidateResolver delegate;
  private DefaultListableBeanFactory factory;
  private String prefix = DEFAULT_PLACEHOLDER_PREFIX;
  private String suffix = DEFAULT_PLACEHOLDER_SUFFIX;
  private String placeholderValuesBeanName;
  private AdapterQualifierAccessor qualifierAccessor;
  
  @Override
  public int getOrder() {
    return PriorityOrdered.HIGHEST_PRECEDENCE;
  }
  
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory factoryCandidate) throws BeansException {
    if(!(factoryCandidate instanceof DefaultListableBeanFactory)) {
      throw new IllegalArgumentException("AdapterInjectingAutowireCandidateResolver requires a DefaultListableBeanFactory or its subclass to operate, got: " + factoryCandidate.getClass().getName());
    }
    
    factory = (DefaultListableBeanFactory) factoryCandidate;
    if(factory.getAutowireCandidateResolver() != this) {
      this.delegate = factory.getAutowireCandidateResolver();
      factory.setAutowireCandidateResolver(this);
    }
    
  }
  
  @Override
  public Object getSuggestedValue(DependencyDescriptor descriptor) {
    Object out = null;
    if(descriptor.getDependencyType() == SpringActiveRecordAdapter.class) {
      out = getAdapterToInject(descriptor);
    } else {
      out = delegate.getSuggestedValue(descriptor);
    }
    
    return out;
  }

  @Override
  public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
    return delegate.isAutowireCandidate(bdHolder, descriptor);
  }
  
  /**
   * Returns an adapter based on the dependency descriptor provided
   */
  private SpringActiveRecordAdapter<?> getAdapterToInject(DependencyDescriptor descriptor) {
    Type type = descriptor.getGenericDependencyType();
    
    // make sure the type can be processed
    if(!(type instanceof ParameterizedType)) {
      throw new IllegalArgumentException("Type must be a parameterized type: " + type);
    }
    
    // extract the base class
    ParameterizedType parameterizedType = (ParameterizedType) type;
    if(parameterizedType.getActualTypeArguments() == null || parameterizedType.getActualTypeArguments().length != 1) {
      throw new IllegalArgumentException("Parameterized type must have one actual type argument: " + parameterizedType);
    }
    Class<?> baseClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
    
    // extract annotations (qualifiers/schema)
    AdapterQualifier adapterAnno = null;
    for(Annotation anno : descriptor.getAnnotations()) {
      if(AdapterQualifier.class.isAssignableFrom(anno.getClass())) {
        adapterAnno = (AdapterQualifier) anno;
      }
    }
    
    return SpringActiveRecordAdapterFactory.getSpringActiveRecordAdapter(factory, baseClass, adapterAnno, getQualifierAccessor());
  }
  
  /**
   * Builds an accessor instance if one has yet been build with the properties from a spring bean
   * with the name provided via the placeholderValuesBeanName property. If no name is provided then
   * an empty properties object is used. The accessor can be used to retrieve values from the
   * AdapterQualifier annotation with placeholders replaced by real values.
   */
  public synchronized AdapterQualifierAccessor getQualifierAccessor() {
    if(qualifierAccessor == null) {
      Properties props = new Properties();
      if(StringUtils.isNotBlank(placeholderValuesBeanName)) {
        props = factory.getBean(placeholderValuesBeanName, Properties.class);
      }
      
      qualifierAccessor = new AdapterQualifierAccessor(prefix, suffix, props);
    }
    
    return qualifierAccessor;
  }
  
  public void setQualifierAccessor(AdapterQualifierAccessor qualifierAccessor) {
    this.qualifierAccessor = qualifierAccessor;
  }

  public String getPrefix() {
    return prefix;
  }

  public void setPrefix(String prefix) {
    this.prefix = prefix;
  }

  public String getSuffix() {
    return suffix;
  }

  public void setSuffix(String suffix) {
    this.suffix = suffix;
  }

  public String getPlaceholderValuesBeanName() {
    return placeholderValuesBeanName;
  }

  public void setPlaceholderValuesBeanName(String placeholderValuesBeanName) {
    this.placeholderValuesBeanName = placeholderValuesBeanName;
  }
  
}
